package com.iagent.bean;

import com.iagent.core.kernel.UrlResource;
import com.iagent.logging.LogFactory;
import com.iagent.logging.Logger;
import com.iagent.register.BeanRegister;
import com.iagent.util.Assert;
import com.iagent.util.ClassUtils;
import com.iagent.util.StringUtils;

import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * like origin url -> ${user.url}/userManagement
 * the environment contain user.url=http://127.0.0.1:9999
 * it is parsing to origin url -> http://127.0.0.1:8099/userManagement
 * if has default value, the expression was ${user.url:http://192.168.1.10:9999}
 * the environment is not contain user.url , parse the first colon
 * so, it is parsing to origin url -> http://192.168.1.10:9999/userManagement
 * @author liujieyu
 * @description load environment information
 * @since 2.1.0
 */
public class Environment {

    private static final Logger logger = LogFactory.getLogger(Environment.class);

    /**
     * Read the files in the following path order by
     * classpath:/iagent.properties
     * classpath:/config/iagent.properties
     * System.getProperty("user.dir") + /iagent.properties
     * System.getProperty("user.dir") + /config/iagent.properties
     */
    private static final String DEFAULT_PROPERTY_SOURCE = "iagent.properties";

    private static final String DEFAULT_CONFIG_PROPERTY_SOURCE = "config/iagent.properties";

    private static final String USER_DIR = System.getProperty("user.dir");

    /**
     * iagent properties path list
     */
    private List<UrlResource> resources = new LinkedList<>();
    /**
     * key-value
     */
    private Map<String, String> env = new ConcurrentHashMap<>(16);

    public Environment() {
        // Load the configuration in turn
        loadEnvironment(DEFAULT_PROPERTY_SOURCE, false);
        loadEnvironment(DEFAULT_CONFIG_PROPERTY_SOURCE, false);
        loadEnvironment(DEFAULT_PROPERTY_SOURCE, true);
        loadEnvironment(DEFAULT_CONFIG_PROPERTY_SOURCE, true);
        loadAll();
    }

    /**
     * @since 2.2.1
     * @param path
     * @param forceDir Load path Specifies whether to add a path
     */
    public void loadEnvironment(String path, boolean forceDir) {
        ClassLoader classLoader = ClassUtils.getClassLoader();
            if (forceDir) {
                try {
                    if (!path.startsWith("/")) {
                        path = "/" + path;
                    }
                    path = USER_DIR + path;
                    if (logger.isDebugEnabled()) {
                        logger.debug("Environment load path is " + path);
                    }
                    resources.add(new UrlResource(path));
                } catch (Exception e) {
                    logger.warn("The path " + path + " is not exists");
                }
            } else {
                try {
                    Enumeration<URL> urlEnumeration = classLoader == null ? ClassLoader.getSystemResources(path) : classLoader.getResources(path);
                    while (urlEnumeration.hasMoreElements()) {
                        URL url = urlEnumeration.nextElement();
                        try {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Environment load path is " + url);
                            }
                            resources.add(new UrlResource(url));
                        } catch (URISyntaxException e) {
                            logger.warn("The url " + url + " is not exists");
                        }
                    }
                } catch (Exception e) {
                    logger.error("Create Environment error", e);
                }
            }
    }

    public Set<String> getProperties() {
        return env.keySet();
    }

    public void addProperty(String key, String value) {
        Assert.notNull(key, "the key is null");
        env.put(key, value);
    }

    /**
     * Gets the value information and returns the default value if it is not found
     *
     * @param key
     * @param defaultValue
     * @return
     * @since 2.1.0
     */
    public String getProperty(String key, String defaultValue) {
        Assert.notNull(key, "get environment key is null");
        return env.get(key) == null ? defaultValue : env.get(key);
    }

    /**
     * Gets the value information and returns null if it is not found
     *
     * @param key
     * @return
     * @since 2.1.0
     */
    public String getProperty(String key) {
        return getProperty(key, null);
    }

    /**
     * Get all the environment variables
     *
     * @return
     * @since 2.1.0
     */
    public Map<String, String> getEnvironment() {
        return env;
    }

    /**
     * Load all Url Resources information
     *
     * @since 2.2.1
     */
    private void loadAll() {
        Iterator<UrlResource> iterator = resources.iterator();
        while (iterator.hasNext()) {
            UrlResource resource = iterator.next();
            InputStream inputStream = null;
            try {
                inputStream = resource.getInputStream();
                load(inputStream);
            } catch (IOException e) {
                logger.error("Get resource inputStream is error, the resource of error is " + resource, e);
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        logger.error("Close input stream is error", e);
                    }
                }
            }
        }
    }

    /**
     * Load the specified path configuration inputStream
     *
     * @param inputStream
     * @since 2.2.1
     */
    public void load(InputStream inputStream) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String content = null;
            while ((content = reader.readLine()) != null) {
                loadContent(content);
            }
            reader.close();
        } catch (IOException e) {
            logger.error("Read input stream is error", e);
        }
    }

    /**
     * Load the specified path configuration file
     *
     * @param filePath
     * @since 2.1.0
     */
    public void load(String filePath) {
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                return;
            }
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            String content = null;
            while ((content = reader.readLine()) != null) {
                loadContent(content);
            }
            reader.close();
        } catch (Throwable throwable) {
            logger.error("read file path error, path [" + filePath + "]", throwable);
        }
    }

    /**
     * Load a single configuration
     *
     * @param content
     * @since 2.1.0
     */
    private void loadContent(String content) {
        if (StringUtils.isNotEmpty(content) && (content.contains(":") || content.contains("=")) && !content.startsWith("#")) {
            int index = content.indexOf("=") == -1 ? content.indexOf(":") : content.indexOf("=");
            if (index == -1) {
                return;
            }
            String key = content.substring(0, index);
            String value = content.substring(index + 1);
            this.env.putIfAbsent(key, value);
        }
    }

    /**
     * Loading the original url
     *
     * @param wrapperBeanRegister
     * @since 2.1.0
     */
    public void refreshOriginUrl(BeanRegister<IagentBeanWrapper> wrapperBeanRegister) {
        String[] allBeanNames = wrapperBeanRegister.getAllBeanNames();
        for (String beanName : allBeanNames) {
            IagentBeanWrapper wrapper = wrapperBeanRegister.getBeanObject(beanName);
            IagentBean bean = wrapper.getBean();
            bean.setUrl(parseEnvironment(bean.getOriginUrl()));
        }
    }

    /**
     * Parse the original url and replace the expression
     *
     * @param originUrl
     * @return
     * @since 2.1.0
     */
    private String parseEnvironment(String originUrl) {
        List<String> expressions = StringUtils.getStringArrayByTags(originUrl, "${", "}");
        if (expressions == null || expressions.isEmpty()) {
            return originUrl;
        }
        for (String express : expressions) {
            String key = null;
            String defaultValue = null;
            if (express.contains(":")) {
                key = express.substring(0, express.indexOf(":"));
                defaultValue = express.substring(express.indexOf(":") + 1);
            } else {
                key = express;
            }
            String value = getProperty(key, defaultValue);
            originUrl = StringUtils.replace(originUrl, "${" + express + "}", value);
        }

        return originUrl;
    }

}
